//
//  AppLogger.swift
//  Pods
//
//  Created by zixun on 16/8/5.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

import Foundation

// MARK: - AppLogger

/// Handles logging called from `aelog` top-level function.
public class AppLogger {
    
    // MARK: Properties
    
    private static let sharedInstance = AppLogger()
    private let settings = AELogSettings()
    private weak var delegate: AELogDelegate?
    private let logQueue = dispatch_queue_create("AELog", nil)
    
    // MARK: API
    
    /// Configures delegate for `AELog` singleton. Use this if you need additional functionality after each line of log.
    public class func launchWithDelegate(delegate: AELogDelegate) {
        AppLogger.sharedInstance.delegate = delegate
    }
    
    public class func logSettings()-> AELogSettings {
        return AppLogger.sharedInstance.settings
    }
    
    
    
    public class func info(message: Any?,
                           path: String = #file,
                           line: Int = #line,
                           function: String = #function) {
        self.sharedInstance.log(level: .Info,
                                path: path,
                                line: line,
                                function: function,
                                message: message)
    }
    
    public class func warning(message: Any?,
                              path: String = #file,
                              line: Int = #line,
                              function: String = #function) {
        self.sharedInstance.log(level: .Warning,
                                path: path,
                                line: line,
                                function: function,
                                message: message)
    }
    
    public class func error(message: Any?,
                            path: String = #file,
                            line: Int = #line,
                            function: String = #function) {
        self.sharedInstance.log(level: .Error,
                                path: path,
                                line: line,
                                function: function,
                                message: message)
    }
    
    
    private func log(level level:AppLoggerLevel,
                            path: String,
                            line: Int,
                            function: String,
                            message: Any?) {
       
        let thread = NSThread.currentThread()
        dispatch_async(logQueue) { [unowned self] in
            if self.settings.enabled {
                let file = self.fileNameForPath(path)
                
                let logLine = AELogLine(level:level,
                                        thread: thread,
                                        file: file,
                                        line: line,
                                        function: function,
                                        message: "\(message == nil ? message : message!)")
                print(logLine.description)
                
                dispatch_async(dispatch_get_main_queue(), {
                    self.delegate?.didLog(logLine)
                })
                
            }
        }
    }
    
    // MARK: Helpers
    
    private func fileNameForPath(path: String) -> String {
        guard let
            fileName = NSURL(fileURLWithPath: path).URLByDeletingPathExtension?.lastPathComponent
            else { return "Unknown" }
        return fileName
    }
    
}

// MARK: - AELogDelegate

/// Forwards logged lines via `didLog:` function.
public protocol AELogDelegate: class {
    func didLog(logLine: AELogLine)
}

// MARK: - AELogLine

public enum AppLoggerLevel {
    case Info
    case Warning
    case Error
}

/// Custom data structure used by `AELog` for log lines.
public struct AELogLine: CustomStringConvertible {
    
    // MARK: Properties
    
    public let level: AppLoggerLevel
    
    /// Timestamp
    public let date: NSDate
    /// Thread
    public let thread: NSThread
    /// Filename (without extension)
    public let file: String
    /// Line of code
    public let line: Int
    /// Function
    public let function: String
    /// Custom message
    public let message: String
    
    private var threadName: String {
        if thread.isMainThread {
            return "Main"
        } else if let name = thread.name where !name.isEmpty {
            return name
        } else {
            return String(format:"%p", thread)
        }
    }
    
    // MARK: Init
    
    private init(level:AppLoggerLevel, thread: NSThread, file: String, line: Int, function: String, message: String) {
        self.date = NSDate()
        self.level = level
        self.thread = thread
        self.file = file
        self.line = line
        self.function = function
        self.message = message
    }
    
    // MARK: - CustomStringConvertible
    
    /// Concatenated text representation of a complete log line
    public var description: String {
        let date = AppLogger.sharedInstance.settings.dateFormatter.stringFromDate(self.date)
        let desc = parse(date: date, thread: threadName, file: file, line: line, function: function, message: message)
        return desc
    }
    
    private func parse(
        date date: String, thread: String, file: String, line: Int, function: String, message: String) -> String {
        let result = AppLogger.sharedInstance.settings.template
            .stringByReplacingOccurrencesOfString("{date}", withString: date)
            .stringByReplacingOccurrencesOfString("{thread}", withString: thread)
            .stringByReplacingOccurrencesOfString("{file}", withString: file)
            .stringByReplacingOccurrencesOfString("{line}", withString: "\(line)")
            .stringByReplacingOccurrencesOfString("{function}", withString: function)
            .stringByReplacingOccurrencesOfString("{message}", withString: message)
        return result
    }
    
}

// MARK: - AELogSettings

/**
 Helper for accessing settings from the external file.
 
 Create `AELog.plist` dictionary file and add it to your target.
 
 There is `Key` struct which contains possible keys for all settings.
 */
public class AELogSettings {
    
    // MARK: Constants
    
    private struct Default {
        private static let Enabled = true
        private static let DateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
        private static let Template = "{date} -- [{thread}] {file} ({line}) -> {function} > {message}"
    }
    
    // MARK: Properties
    
    private let dateFormatter = NSDateFormatter()
    
    
    // MARK: Init
    
    public init() {
        dateFormatter.dateFormat = dateFormat
    }
    
    
    // MARK: Settings
    public var enabled:Bool = Default.Enabled
    
    public var dateFormat: String = Default.DateFormat
    
    public var template: String = Default.Template
}